ITHOM進入尾聲了,最後來玩下有趣的東西吧
這次我們要做的是實現網域內溝通,只要在同個 ws server下client端可相互傳遞訊息
主要內容為
1.Server 端:分發唯一 ID,並管理每個連接的 WebSocket 實例。
2.Client 端:每個客戶端顯示自己的 ID,並可以選擇發送消息到指定的目標 ID。
3.雙向通信:客戶端之間可以通過 WebSocket 實現點對點通信。
先看成品
直接上code
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
let connectionCounter = 0; // 用於分配唯一ID
const clients = {}; // 存儲連接的客戶端 { id: WebSocket }
wss.on("connection", (ws) => {
connectionCounter++;
const clientId = connectionCounter;
clients[clientId] = ws; // 記錄客戶端的連接
// 發送給客戶端,告知其ID
ws.send(JSON.stringify({ type: "welcome", id: clientId, message: `歡迎!你的客戶端ID是: ${clientId}` }));
console.log(`客戶端 ${clientId} 已連接`);
// 發送給所有客戶端,告知新客戶端連接
for (let id in clients) {
clients[id].send(JSON.stringify({ type: "connectedClients", clients: Object.keys(clients) }));
}
// 當收到客戶端消息時
ws.on("message", (data) => {
const message = JSON.parse(data);
if (message.type === "sendMessage") {
const targetId = message.targetId;
const content = message.content;
if (clients[targetId]) {
// 發送消息給目標ID
clients[targetId].send(JSON.stringify({ type: "message", from: clientId, content }));
} else {
ws.send(JSON.stringify({ type: "error", message: `ID為 ${targetId} 的客戶端不存在` }));
}
}
});
ws.on("close", () => {
console.log(`客戶端 ${clientId} 已斷開連接`);
delete clients[clientId]; // 移除客戶端記錄
for (let id in clients) {
clients[id].send(JSON.stringify({ type: "connectedClients", clients: Object.keys(clients) }));
}
});
});
console.log("WebSocket 服務器運行在 ws://localhost:8080");
clients[clientId] = ws
import React, { useState, useEffect, useRef } from 'react';
function App() {
const [clientId, setClientId] = useState(null); // 保存自己的ID
const [connectedClients, setConnectedClients] = useState([]); // 保存當前連接的客戶端ID
const [messages, setMessages] = useState([]); // 保存接收到的消息
const [targetId, setTargetId] = useState(''); // 目標ID
const [messageContent, setMessageContent] = useState(''); // 發送的消息內容
const ws = useRef(null);
useEffect(() => {
// 建立 WebSocket 連接
ws.current = new WebSocket('ws://localhost:8080');
ws.current.onopen = () => {
console.log('WebSocket 連接成功');
};
// 當收到服務器的消息時
ws.current.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'welcome') {
setClientId(data.id); // 保存自己的ID
setMessages((prevMessages) => [...prevMessages, data.message]);
}
if(data.type === 'connectedClients') {
setConnectedClients(data.clients);
}
if (data.type === 'message') {
setMessages((prevMessages) => [
...prevMessages,
`來自客戶端 ${data.from} 的消息: ${data.content}`
]);
}
if (data.type === 'error') {
setMessages((prevMessages) => [...prevMessages, `錯誤: ${data.message}`]);
}
};
ws.current.onclose = () => {
console.log('WebSocket 連接關閉');
};
ws.current.onerror = (error) => {
console.error('WebSocket 錯誤:', error);
};
return () => {
if (ws.current) {
ws.current.close();
}
};
}, []);
// 發送消息到指定ID的客戶端
const sendMessage = () => {
if (ws.current && ws.current.readyState === WebSocket.OPEN) {
const message = {
type: 'sendMessage',
targetId: targetId,
content: messageContent
};
ws.current.send(JSON.stringify(message)); // 發送消息到服務器
}
};
return (
<div style={{ textAlign: 'center', marginTop: '50px' }}>
<h1>WebSocket 點對點通信</h1>
<h2>你的客戶端ID是: {clientId}</h2>
<div>
<h3>目前連接的客戶</h3>
{connectedClients.map((client, index) => (
<span key={index}>id:{client},</span>))}
</div>
<div style={{ maxHeight: '200px', overflowY: 'auto', border: '1px solid #ccc', padding: '10px' }}>
<h3>消息紀錄</h3>
{messages.map((msg, index) => (
<p key={index}>{msg}</p>
))}
</div>
<input
type="text"
value={targetId}
onChange={(e) => setTargetId(e.target.value)}
placeholder="輸入目標ID"
style={{ marginTop: '20px' }}
/>
<br />
<input
type="text"
value={messageContent}
onChange={(e) => setMessageContent(e.target.value)}
placeholder="輸入要發送的內容"
style={{ marginTop: '10px' }}
/>
<br />
<button onClick={sendMessage} style={{ marginTop: '10px' }}>
發送消息
</button>
</div>
);
}
export default App;
主要幾點:
useRef
用於保持 WebSocket 連接的持久性,即使 React 組件重新渲染也不會導致 WebSocket 重新初始化。這樣可以確保 WebSocket 連接只建立一次。ws.current
是儲存 WebSocket 連接本身,最後確認連接狀態來決定是否發送訊息。useState
來快速渲染這樣簡易的client端間的相互溝通就完成啦
今天就這樣結束囉